package elms

import (
	"context"
	"crypto/elliptic"
	"encoding/json"
	"github.com/cenk/backoff"
	"github.com/containous/mux"
	"github.com/hyperboloide/lk"
	"github.com/traefik/traefik/job"
	"github.com/traefik/traefik/log"
	"github.com/traefik/traefik/safe"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"io/ioutil"
	"net/http"
	"time"
)

var (
	session *mgo.Session
	pk      *lk.PublicKey
)

type Handler struct {
	Provider
	MongodbUri string `description:"MongoDB connect uri"`
	EntryPoint string `description:"EntryPoint" export:"true"`
}

type License struct {
	LicenseKey       string    `json:"licenseKey,omitempty" bson:"licenseKey,omitempty"`
	LicenseType      string    `json:"licenseType,omitempty" bson:"licenseType,omitempty"`
	OrganizationName string    `json:"organizationName,omitempty" bson:"organizationName,omitempty"`
	Expires          time.Time `json:"expires,omitempty" bson:"expires,omitempty"`
	Contact          Contact   `json:"contact,omitempty" bson:"contact,omitempty"`
	Expired          bool      `json:"expired" bson:"expired"`
}

type Contact struct {
	Name  string `json:"name,omitempty" bson:"name,omitempty"`
	Email string `json:"email,omitempty" bson:"email,omitempty"`
	Phone string `json:"phone,omitempty" bson:"phone,omitempty"`
}

func init() {
	lk.Curve = elliptic.P521
	pk, _ = lk.PublicKeyFromB64String("BAEuhMu+kP1J3YVRGApWVxfGt2zslcdAOmbMtBuWQSFAQc/nPBmRAgU8tgwC2lMMAJDk3i5qmH7Vi85H6onG5chROQG19NsZdL1jXZvs/204LYNChrp5gNJpoMAOSZQshTcao61XuuYcLhNDW91t1rxf1nrNYqxDn4pmWgtUJNbynL+36Q==")
}

func Init(elms *Handler, pool *safe.Pool) {
	var err error
	session, err = mgo.Dial(elms.MongodbUri)
	if err != nil {
		log.Fatalf("Failed connect mongodb: %v", err)
	}

	handleCanceled := func(ctx context.Context, err error) error {
		if ctx.Err() == context.Canceled || err == context.Canceled {
			return nil
		}
		return err
	}

	pool.Go(func(stop chan bool) {
		ctx, cancel := context.WithCancel(context.Background())
		safe.Go(func() {
			<-stop
			cancel()
		})

		operation := func() error {
			verifyLicense()
			reload := time.NewTicker(time.Second * 10)
			defer reload.Stop()
			for {
				log.Debug("Watching License...")
				select {
				case <-reload.C:
					verifyLicense()
				case <-ctx.Done():
					return handleCanceled(ctx, ctx.Err())
				}
			}
		}
		notify := func(err error, time time.Duration) {
			log.Errorf("elms plugin error: %s time: %v", err.Error(), time)
		}

		err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
		if err != nil {
			log.Errorf("Failed to init elms plugin. %s", err.Error())
		}
	})
}

func verifyLicense() {
	license := getLicense()
	if license == nil {
		paymentRequired = true
	} else {
		paymentRequired = license.Expired
	}
}

func getLicense() *License {
	conn := session.Copy()
	defer conn.Close()

	lics := new(License)
	_ = conn.DB("ABCDE_db").C("license").Find(bson.M{}).One(lics)
	return decrypt(lics.LicenseKey)
}

// AddRoutes add api routes on a router
func (p Handler) AddRoutes(router *mux.Router) {
	// health route
	router.Methods(http.MethodGet).Path("/api/elms/admin/license").HandlerFunc(p.getLicenseHandler)
	router.Methods(http.MethodPut).Path("/api/elms/admin/license").HandlerFunc(p.updateLicenseHandler)
}

func (p *Handler) getLicenseHandler(rw http.ResponseWriter, r *http.Request) {
	license := getLicense()
	if license == nil {
		ErrorHandler(rw, r, NewError(InvalidParameter, "licenseKey"))
		return
	}

	_ = defaultRender.JSON(rw, http.StatusOK, bson.M{
		"result": license,
	})
}

func decrypt(licenseKey string) *License {
	if licenseKey == "" {
		return nil
	}

	license, err := lk.LicenseFromB64String(licenseKey)

	if err != nil {
		log.Warnln("bad license key", err)
		return nil
	}

	if verify, err := license.Verify(pk); err != nil || !verify {
		return nil
	}

	lics := new(License)
	if err = json.Unmarshal(license.Data, lics); err == nil {
		lics.LicenseKey = licenseKey
		lics.Expired = time.Now().After(lics.Expires)
		paymentRequired = lics.Expired
		return lics
	}

	return nil
}

func (p *Handler) updateLicenseHandler(w http.ResponseWriter, r *http.Request) {
	request := new(License)
	body, _ := ioutil.ReadAll(r.Body)
	if err := json.Unmarshal(body, request); err == nil {
		if request.LicenseKey == "" {
			ErrorHandler(w, r, NewError(MissRequiredParameter, "licenseKey"))
		} else {
			lics := decrypt(request.LicenseKey)
			if lics == nil {
				ErrorHandler(w, r, NewError(InvalidParameter, "licenseKey"))
				return
			}
			conn := session.Copy()
			defer conn.Close()

			_, _ = conn.DB("ABCDE_db").C("license").Upsert(bson.M{}, lics)

			_ = defaultRender.JSON(w, http.StatusOK, bson.M{
				"result": lics,
			})
		}
	} else {
		ErrorHandler(w, r, NewError(InvalidRequest))
	}

}
